import { sm2, sm3, sm4 } from 'sm-crypto'

const cipherMode = 1 // 1 - C1C3C2，0 - C1C2C3，默认为1

class SMUtil {
  /**
   * SM2 加密
   * @param {string} publicKey - 公钥（Base64 编码）
   * @param {string} value - 需要加密的数据
   * @returns {string} 加密后的数据（Base64 编码）
   */
  static sm2Encrypt(publicKey, value) {
    let encrypt = sm2.doEncrypt(value, base64URLSafeToHex(publicKey), cipherMode)
    if (!encrypt.startsWith('04')) {
      encrypt = '04' + encrypt
    }
    return hexToBase64URLSafe(encrypt) // 使用 C1C3C2 模式
  }

  /**
   * SM2 解密
   * @param {string} privateKey - 私钥（Base64 编码）
   * @param {string} value - 加密后的数据（Base64 编码）
   * @returns {string} 解密后的数据
   */
  static sm2Decrypt(privateKey, value) {
    let decrypt = base64URLSafeToHex(value)
    if (decrypt.startsWith('04')) {
      decrypt = decrypt.slice(2)
    }
    return sm2.doDecrypt(decrypt, base64URLSafeToHex(privateKey), cipherMode) // 使用 C1C3C2 模式
  }

  /**
   * SM3 哈希
   * @param {string} data - 需要哈希的数据
   * @returns {string} 哈希值（十六进制字符串）
   */
  static sm3Hash(data) {
    return sm3(data).toUpperCase()
  }

  /**
   * SM4 加密
   * @param {string} key - 密钥（Base64 编码）
   * @param {string} iv - 初始向量（Base64 编码）
   * @param {string} data - 需要加密的数据
   * @returns {string} 加密后的数据（Base64 编码）
   */
  static sm4Encrypt(key, iv, data) {
    return hexToBase64URLSafe(
      sm4.encrypt(data, base64URLSafeToHex(key), {
        iv: base64URLSafeToHex(iv),
        mode: 'cbc',
        padding: 'pkcs#7',
      }),
    )
  }

  /**
   * SM4 解密
   * @param {string} key - 密钥（Base64 编码）
   * @param {string} iv - 初始向量（Base64 编码）
   * @param {string} encryptedData - 加密后的数据（Base64 编码）
   * @returns {string} 解密后的数据
   */
  static sm4Decrypt(key, iv, encryptedData) {
    return sm4.decrypt(base64URLSafeToHex(encryptedData), base64URLSafeToHex(key), {
      iv: base64URLSafeToHex(iv),
      mode: 'cbc',
      padding: 'pkcs#7',
    })
  }

  /**
   * 加密（与后端配套）
   * @param {string} publicKey - SM2 公钥（Base64 编码）
   * @param {string} sm4Key - SM4 密钥（Base64 编码）
   * @param {string} sm4Iv - SM4 初始向量（Base64 编码）
   * @param {Object} data - 需要加密的数据
   * @returns {string} 加密后的数据（Base64 编码）
   */
  static encrypt(publicKey, sm4Key, sm4Iv, data) {
    const dataStr = JSON.stringify(data)
    // SM2 加密
    const encryptedData = this.sm2Encrypt(publicKey, dataStr)
    if (!encryptedData) return null
    // SM3 加签
    const sign = this.sm3Hash(dataStr)
    if (!sign) return null
    // SM4 加密
    const jsonStr = JSON.stringify({ data: encryptedData, sign })
    return this.sm4Encrypt(sm4Key, sm4Iv, jsonStr)
  }

  /**
   * 解密（与后端配套）
   * @param {string} privateKey - SM2 私钥（Base64 编码）
   * @param {string} sm4Key - SM4 密钥（Base64 编码）
   * @param {string} sm4Iv - SM4 初始向量（Base64 编码）
   * @param {string} encryptedData - 加密后的数据（Base64 编码）
   * @returns {Object} 解密后的数据
   */
  static decrypt(privateKey, sm4Key, sm4Iv, encryptedData) {
    // SM4 解密
    const jsonStr = this.sm4Decrypt(sm4Key, sm4Iv, encryptedData)
    if (!jsonStr) return null
    const json = JSON.parse(jsonStr)
    const encrypted = json.data
    const sign = json.sign
    if (!encrypted || !sign) return null
    // SM2 解密
    const decryptedData = this.sm2Decrypt(privateKey, encrypted)
    // SM3 验签
    return this.sm3Hash(decryptedData) === sign ? JSON.parse(decryptedData) : null
  }
}

// 16 进制字符串转 URL-safe Base64
function hexToBase64URLSafe(hex) {
  const bytes = new Uint8Array(hex.length / 2)
  for (let i = 0; i < hex.length; i += 2) {
    bytes[i / 2] = parseInt(hex.substr(i, 2), 16)
  }
  const base64 = btoa(String.fromCharCode.apply(null, bytes))
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

// URL-safe Base64 转 16 进制字符串
function base64URLSafeToHex(base64) {
  const standardBase64 = base64.replace(/-/g, '+').replace(/_/g, '/')
  const padding = standardBase64.length % 4
  const paddedBase64 = standardBase64 + '='.repeat((4 - padding) % 4)
  const byteString = atob(paddedBase64)
  const bytes = new Uint8Array(byteString.length)
  for (let i = 0; i < byteString.length; i++) {
    bytes[i] = byteString.charCodeAt(i)
  }
  let hex = ''
  for (let i = 0; i < bytes.length; i++) {
    const hexByte = bytes[i].toString(16).padStart(2, '0')
    hex += hexByte
  }
  return hex
}

export default SMUtil
